fix(web): expose GitHub install handlers, simplify Alpine loader, explicit Flask threading#305
Merged
Conversation
…licit Flask threading A user reported that buttons in the v3 web UI were unresponsive in Safari after a fresh install. The screenshots showed Alpine.js actually running fine end-to-end — the real issues are a narrow handler-exposure bug and some latent brittleness worth cleaning up at the same time. plugins_manager.js: attachInstallButtonHandler and setupGitHubInstallHandlers were declared inside the main IIFE, but the typeof guards that tried to expose them on window ran *outside* the IIFE, so typeof always evaluated to 'undefined' and the assignments were silently skipped. The GitHub "Install from URL" button therefore had no click handler and the console printed [FALLBACK] attachInstallButtonHandler not available on window on every load. Fixed by assigning window.attachInstallButtonHandler and window.setupGitHubInstallHandlers *inside* the IIFE just before it closes, and removing the dead outside-the-IIFE guards. base.html: the Alpine.js loader was a 50-line dynamic-script + deferLoadingAlpine + isAPMode branching block. script.defer = true on a dynamically-inserted <script> is a no-op (dynamic scripts are always async), the deferLoadingAlpine wrapper was cargo-culted, and the AP-mode branching reached out to unpkg unnecessarily on LAN installs even though alpinejs.min.js already ships in web_interface/static/v3/js/. Replaced with a single <script defer src="..."> tag pointing at the local file plus a small window-load rescue that only pulls the CDN copy if window.Alpine is still undefined. start.py / app.py: app.run() has defaulted to threaded=True since Flask 1.0 so this is not a behavior change, but the two long-lived /api/v3/stream/* SSE endpoints would starve every other request under a single-threaded server. Setting threaded=True explicitly makes the intent self-documenting and guards against future regressions. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A user reported that the Plugin Manager button (and, they said, "every button") was unresponsive in Safari after a fresh install. Their screenshots actually showed Alpine.js running fine end-to-end — the real problems are a narrow handler-exposure bug, some latent brittleness in the Alpine loader, and an implicit reliance on Flask's default threading for SSE. Fixing all three in one pass.
plugins_manager.js—attachInstallButtonHandlernever exposed onwindow(the bug the reporter's console actually shows). The function is declared at line 5756 inside the main IIFE that closes at line 7164. Thetypeofguards meant to copy it towindowsat outside the IIFE at lines 7394-7401, sotypeof attachInstallButtonHandleralways evaluated to'undefined'and the assignment was silently skipped. The "Install from GitHub URL" button therefore had no click handler and[FALLBACK] attachInstallButtonHandler not available on windowfired on every page load. Fixed by assigningwindow.attachInstallButtonHandlerandwindow.setupGitHubInstallHandlersinside the IIFE, and removing the dead outside-the-IIFE guards.base.html— simplified Alpine.js loader. Replaced the ~50-line dynamic-script +deferLoadingAlpine+isAPModebranching block with a single<script defer src="{{ url_for('static', filename='v3/js/alpinejs.min.js') }}"></script>plus a smallwindow.loadrescue that only pulls the unpkg CDN copy ifwindow.Alpineis still undefined.script.defer = trueon a dynamically-inserted<script>was a no-op (dynamic scripts are always async), thedeferLoadingAlpinewrapper was cargo-culted, and the AP-mode branch reached out to unpkg unnecessarily on LAN installs even thoughalpinejs.min.jsalready ships in the repo atweb_interface/static/v3/js/.start.py/app.py— explicitthreaded=Trueonapp.run(...). Not a behavior change (Flask has defaulted tothreaded=Truesince 1.0), but makes it self-documenting. The two long-lived/api/v3/stream/*SSE endpoints would starve every other request under a single-threaded dev server, and this guards against future regressions if someone disables threading.Not changed (intentional)
[RENDER] installed-plugins-grid not yet available, deferring render until plugin tab loadswarning atplugins_manager.js:1378is expected behavior (the grid lives inside an HTMX-lazy-loaded tab) and is left in place. Can be downgraded topluginLogin a separate noise-cleanup PR if desired.systemd/ledmatrix-web.servicesetsEnvironment=USE_THREADING=1, but that env var is never read anywhere in the repo. Left as-is since it's dead config and touching the systemd template affects install scripts.Does this fix the reporter's symptom?
Partially. Fix #1 will silence the
[FALLBACK]warning they're seeing and restore the GitHub "Install from URL" button. But their claim was "Plugin Manager button or any button won't work", and since the screenshots prove Alpine's click layer is healthy on their machine, the "any button" part is almost certainly something environment-specific (Safari version, a stuck overlay, an uncaught exception scrolled out of the console) that can't be diagnosed from the screenshots alone. A follow-up diagnostic message has been sent to collect more data from them.Test plan
git pull, hard-reload (Cmd+Opt+R in Safari), confirm the Plugin Manager tab still opens and renders.typeof window.attachInstallButtonHandler === 'function'andtypeof window.setupGitHubInstallHandlers === 'function'.[FALLBACK] attachInstallButtonHandler not available on windowwarning no longer appears in the console.alpinejs.min.jsloads from/static/v3/js/(200) and no request goes tounpkg.com.window.Alpineis defined and tab switching still works across Overview / Settings / Logs / Plugin Manager.192.168.4.1) — the local-first loader should work identically there./api/v3/stream/*SSE connections in the Network tab for several minutes under load — confirm they stay open (Connected indicator green) and don't interrupt other requests.🤖 Generated with Claude Code